﻿using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;

namespace Ue.Extensions
{
    public static class IConvertibleExtensions
    {
        //#region As

        ///// <summary>
        ///// 将对象转换为指定类型， 
        /////对像为null、DBNull.Value或string.Empty时近回default(T)
        ///// </summary>
        ///// <param name="col">要转换的对象</param>
        ///// <returns></returns>
        public static T As<T>(this object col)
        {
            return (T)col.As(typeof(T), () => default(T));
        }
        public static T As<T>(this object col,T defaultValue)
        {
            return (T)col.As(typeof(T), defaultValue);
        }
        public static object As(this object col, Type targetType)
        {
            return col.As(targetType, () => BuildDefaultValue(targetType));
        }
        public static object As(this object col, Type targetType, object defaultValue)
        {
            return col.As(targetType, () => defaultValue);
        }
        private static object As(this object col, Type targetType, Func<object> acquireDefaultValue)
        {
            //无法转换时，直接返回默认值
            if (col == null
                || col == DBNull.Value
                || targetType == null)
            {
                return acquireDefaultValue();
            }

            Type colType = col.GetType();
            // 如果为Nullable<T>类型，则将targetType更改为Nullable<T>.UnderlyingType
            Type resultType = Nullable.GetUnderlyingType(targetType) ?? targetType;
      
            //如果当前类型和要转换到的类型相同，则直接返回对象
            //当是从子类转为父类时，直接转换。
            //当从继承转类转换为接口时，直接转换
            if (colType == resultType ||
                colType.IsSubclassOf(resultType) ||
                (resultType.IsInterface && colType.GetInterface(resultType.Name) != null))
            {
                return col;
            }

            ///将对象转换为字符串
            if (resultType == typeof(string))
            {
                return col.ToString();
            }

            if (resultType.IsEnum)
            {
                return Enum.Parse(resultType, col.ToString(), true);
            }

            if (col is string)
            {
                return IConvertibleExtensions.As((string)col, resultType, acquireDefaultValue);
            }

            try
            {
                return Convert.ChangeType(col, resultType);
            }
            catch
            {
                return acquireDefaultValue();
            }
        }

        //public static T As<T>(this string col, object acquireDefaultValue)
        //{
        //    return (T)IConvertibleExtensions.As(col, typeof(T), acquireDefaultValue);
        //}
        //private static object As(this string col, Type targetType, object acquireDefaultValue)
        //{
        //    return col.As(targetType, () => acquireDefaultValue);
        //}
        public static object As(this string col, Type targetType, Func<object> acquireDefaultValue)
        {
            try
            {
                return StringAsMaps.Maps.ContainsKey(targetType)
                    ? StringAsMaps.Maps[targetType](col)
                    : Convert.ChangeType(col, targetType);
            }
            catch (FormatException)
            {
                return acquireDefaultValue();
            }
        }

        private static object BuildDefaultValue(Type targetType)
        {
            if (targetType == typeof(string))
            {
                return string.Empty;
            }
            else
            {
                try
                {
                    return Activator.CreateInstance(targetType);
                }
                catch (MissingMethodException)
                {
                    return null;
                }
            }
        }

        public class StringAsMaps
        {
            public readonly static IDictionary<Type,Func<string,object>> Maps;

            static StringAsMaps()
            {
                Maps = new ConcurrentDictionary<Type, Func<string, object>>();

                Maps.Add(typeof(Guid), item => Guid.Parse(item));
                Maps.Add(typeof(Uri), item => new Uri(item));
                Maps.Add(typeof(TimeSpan), item => TimeSpan.Parse(item));
                Maps.Add(typeof(Type), item => Type.GetType(item));
                Maps.Add(typeof(DateTimeOffset), item => DateTimeOffset.Parse(item));
                Maps.Add(typeof(bool), item =>
                {
                    int number;
                    if (int.TryParse(item, out number))
                    {
                        return number > 0;
                    }

                    return Convert.ToBoolean(item);
                });
                Maps.Add(typeof(DateTime), item =>
                {
                    if (item.IsInteger() && (item.Length == 4 || item.Length == 6 || item.Length == 8))
                    {
                        item = item.Insert(4, "-");

                        if (item.Length > 6)
                        {
                            item = item.Insert(7, "-");
                        }
                        else
                        {
                            item += "01-";
                        }

                        if (item.Length <= 8)
                        {
                            item += "01";
                        }
                    }

                    return DateTime.Parse(item);
                });
            }
        }
    }
}
